//------------------------------------------------------------------------------ 
//      Copyright (c) 2004-2009, Be Nanashi-San 
// 
//      Permission is hereby granted, free of charge, to any person obtaining a 
//      copy of this software and associated documentation files (the "Software"), 
//      to deal in the Software without restriction, including without limitation 
//      the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//      and/or sell copies of the Software, and to permit persons to whom the 
//      Software is furnished to do so, subject to the following conditions: 
// 
//      The above copyright notice and this permission notice shall be included in 
//      all copies or substantial portions of the Software. 
// 
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
//      DEALINGS IN THE SOFTWARE. 
// 
//      File Name:	NetUtil.cpp
//      Author: 	Be Nanashi-San 
//      Description:	 
//------------------------------------------------------------------------------

#include "config.h"

// zeta 2 スレ@718のBe名無しさんより。R5.03+BONE対応のため。
// in.hをNetBuffer.hの前にinclude
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <3rdparty/zconf.h>
#include <3rdparty/zlib.h>
#include <DataIO.h>
#include <UTF8.h>

#define DEBUG 1
#include <Debug.h>

#include "NetUtil.h"
#include "html2dat.h"


////////////////////////////////////////////////////////////////////////////////
// urlencode
// シフトJISに変換した後、URLエンコードを行う。
// 20051115 Utilに移動
// 20060729 NetUtilに移動
////////////////////////////////////////////////////////////////////////////////
void 
Urlescape(const char *in, BString &out)
{
	//Zeta 2すれBe名無しさん@702より、strlen()を回避。
	if (!in || !(*in)){
		return;
	}
	
	int32 srclen = strlen(in);
	int32 dstlen = srclen * 2 ;
	int32 state;
	char *convdst = static_cast<char *>(malloc(dstlen));
	
	convert_from_utf8(B_SJIS_CONVERSION, in, &srclen, convdst, &dstlen, &state);

	char *s = convdst;
	for(int32 i = 0; i < dstlen; i++, s++){
		//半角スペースは+に変換。
		if (*s == ' '){
			out += '+';
		}else{
		//その他%16進数標記に変換。
			unsigned char ch = static_cast<unsigned char >(*s);
			static const char * const num = "0123456789abcdef";
			out +='%';
			out += num[ch / 16];
			out += num[ch % 16];
		}
	}

	free (convdst);
}

////////////////////////////////////////////////////////////////////////////////
// uncompress
// gzip伸張を行う。（ヘッダサイズは10に固定。）
// 成功時、伸長後のサイズを返す。
// 参考資料。
// http://www.sra.co.jp/people/m-kasahr/zlib/
// http://mi.med.tohoku.ac.jp/~satodai/w3m-dev/200108.month/2186.html
// http://ghanyan.monazilla.org/gzip.html
////////////////////////////////////////////////////////////////////////////////
static status_t
uncompress(const char *from , size_t size, BMallocIO & to)
{
unsigned char buf[4096]; 
z_stream zs; 

	zs.zalloc = Z_NULL; 
	zs.zfree = Z_NULL; 
	zs.opaque = Z_NULL; 
	zs.next_in = Z_NULL; 
	zs.avail_in = 0; 

	if (inflateInit2(&zs, -MAX_WBITS) != Z_OK){ 
	   return B_ERROR; 
	}
	
	const int hsize = 10;
	zs.next_in = (Bytef*)(from) + hsize; 
	zs.avail_in = size - hsize; 
	zs.next_out = buf; 
	zs.avail_out = sizeof(buf); 

	for(;;) { 
		int status = inflate(&zs, Z_NO_FLUSH); 
		if (status == Z_STREAM_END) { 
			if (sizeof(buf) - zs.avail_out) {
				to.Write(buf, sizeof(buf) - zs.avail_out); 
			}
			break; 
		} 
		if (status != Z_OK) {
			PRINT(("\n\n\nzliberr= %s  hsize=%d\n", zs.msg, hsize));
			return B_ERROR; 
		}
		if (zs.avail_out == 0) { 
			to.Write(buf, sizeof(buf)); 
			zs.next_out = buf; 
			zs.avail_out = sizeof(buf); 
		} 
	}
	 
	inflateEnd(&zs); 
	return zs.total_out; 
}
////////////////////////////////////////////////////////////////////////////////
// checkResponseHeader
// 返答ヘッダの解析を行う。
// 各種情報をhttp_info構造体に返す。
////////////////////////////////////////////////////////////////////////////////
static void 
checkResponseHeader(const char *responseheader, http_info &info)
{
	info.gzipped = false;
	info.body_length = 	0;
	info.modified = "";
	const char *s = responseheader;

	for(;;){
		//ヘッダー終了チェック
		if (*s =='\r' && *(s + 1) == '\n' && *(s + 2)=='\r' && *(s + 3) == '\n' ){
			s += 4;
			break;
		}
		
		//改行スキップ
		if (*s =='\r' && *(s + 1) == '\n' ){
			s += 2;
		}
		
		//ステータスコード取得
		if (*s =='H' && strncmp("HTTP/", s, 5)==0){
			s += 9;
			char buf[4];
			char *ss = buf;
			// ステータスコードは3byte 整数
			*ss++ = *s++;
			*ss++ = *s++;
			*ss++ = *s++;
			*ss = '\0';
			info.status = atoi(buf);
		}

		//最終更新日付取得
		if (*s =='L' && strncmp("Last-Modified:", s, 14)==0){
			s += 15;
			char buf[64];
			char *ss = buf;
			while(*s != '\r') *ss++ = *s++;
			*ss = '\0';
			info.modified.SetTo(buf);
		}
		//gzip圧縮のチェック
		if (*s =='C' && strncmp("Content-Encoding:", s, 17)==0){
			s += 18;
			if(strncmp("gzip", s, 4)==0){
				info.gzipped = true;
				s += 4;
			}
		}
		//bodyのサイズ（非gzip圧縮時のみ）
		if (*s =='C' && strncmp("Content-Length:", s, 15)==0){
			s += 16;
			char buf[8];
			char *ss = buf;
			while(*s != '\r') *ss++ = *s++;
			*ss = '\0';
			info.body_length = atoi(buf);
		}
		
		while(*s != '\r') s++;
	}
	
	//ヘッダー長
	info.header_length = s - responseheader;
}
////////////////////////////////////////////////////////////////////////////////
// uri2host
// uri2hostをhostとそれ以外に分割。
//
////////////////////////////////////////////////////////////////////////////////
void
uri2host(const char *uri, char *host, char* subdir)
{
//TODO:strtok()が使えるか？？
//20051022:終端が/で終わっていないときの処理を追加。
	if (!uri) return;
	char *p = host;
	while(*uri && *uri != '/') *p++ = *uri++;
	*p = '\0';

	if (!subdir) return;
	if (*uri)	uri++;
	p = subdir;
	if(*uri != 0){
		while(*uri != '/' && *uri) *p++ = *uri++;
	}
	*p = '\0';
}
////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
static void
initexheader(char *exheader, http_info &info)
{
	//20051113初期化を追加。
	*exheader = 0;
	
	//差分取得
	if (info.body_length > 0){
		char buf[32];
		// サイズを -1 するのはあぼ～ん検出用。
		// 参考：と～く２ちゃんねる（http://age.s22.xrea.com/talk2ch/）
		sprintf(buf, "Range: bytes=%d-\r\n", info.body_length - 1);
		strcat(exheader, buf);
		info.gzipped = false; // 差分は圧縮されない？？
	}
	//圧縮転送指定
	if (info.gzipped == true){
		strcat(exheader, "Accept-Encoding: gzip\r\n"); 
	}
	//最終更新時刻
	if (info.modified.Length()){
		strcat(exheader, "If-Modified-Since: ");
		strcat(exheader, info.modified.String());
		strcat(exheader, "\r\n");
	}

}
////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//TODO:別スレッド化。

#define BUF_LEN 1024

status_t
getData(const char *requestheader, const char *host, BMallocIO &nbuf)
{
    int s; 
    struct hostent *servhost;
    struct sockaddr_in server;
 
	PRINT(("header:%s\n host:%s\n", requestheader, host));
    //20060708
    nbuf.SetSize(0);
    nbuf.Seek(0, SEEK_SET);    //20060727:SetSize(0)だけではだめ。
    
    servhost = gethostbyname(host);
    if ( servhost == NULL ){
        return B_ERROR;
	}
	
    memset(&server, 0, sizeof(server));

    server.sin_family = AF_INET;
 	memcpy(&server.sin_addr, servhost->h_addr, servhost->h_length);
    server.sin_port = htons(80);

    if ( ( s = socket(AF_INET, SOCK_STREAM, 0) ) < 0 ){
        return B_ERROR;
    }
 
    if ( connect(s, (struct sockaddr *)&server, sizeof(server)) < 0){
		close(s);
        return B_ERROR;
    }
    send(s, requestheader, strlen(requestheader), 0);

    for (;;){
        char buf[BUF_LEN];
        int read_size;
        read_size = recv(s, buf, BUF_LEN, 0);
        if ( read_size <= 0 )	break;
        nbuf.Write(buf, read_size);
    }
	close(s);
    return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
// get
// ネットワークより各種データを取得。
//
////////////////////////////////////////////////////////////////////////////////
int
get(const char *host, const char *filename, BMallocIO &outbuf, http_info &info)
{
BMallocIO nbuf;
nbuf.SetBlockSize(1024*4); //20060803

char exheader[256], requestheader[512];

	initexheader(exheader, info);
	sprintf(requestheader,
			"GET /%s HTTP/1.0\r\n"
			"Host: %s:80\r\n"
			"User-Agent: Monazilla/1.00 2ch+/" VERSION_2CHPLUS "\r\n"
			"Connection: close\r\n"
			"%s"
			"\r\n", filename, host, exheader);
							
	if (getData(requestheader, host, nbuf) != B_OK) return B_ERROR;
	
	size_t len = nbuf.BufferLength();
	const char *buf = static_cast <const char *>(nbuf.Buffer());
	
	checkResponseHeader(buf, info);
		
	PRINT(("%s\n size %d sts:%d, gziped:%d, modified:%s\n", buf, info.header_length, info.status, info.gzipped, info.modified.String()));
	if (info.status != HTTP_OK //OK
	&& info.status != HTTP_PARTIAL_CONTENT //差分あり
	) {return info.status;}

	status_t sts;
	if(info.gzipped == true){
		sts = uncompress(buf + info.header_length, len - info.header_length, outbuf);
		info.body_length = sts;
	}else{
		if(info.status == HTTP_PARTIAL_CONTENT && *(buf + info.header_length) != '\n'){
			// 新着あり + あぼ～んあり。
			// 参考：と～く２ちゃんねる（http://age.s22.xrea.com/talk2ch/）
			return -HTTP_PARTIAL_CONTENT;
		}
		//20051230:１の名前欄がおかしいのを修正。
		//20060127:info.body_length-1に再修正。
		sts = outbuf.Write(buf + info.header_length, info.body_length);	
	}
	
	if (sts <= 0) return B_ERROR;
//	PRINT(("OKOK\n"));

	return info.status; // HTTP_OKまたはHTTP_PARTIAL_CONTENT
}
////////////////////////////////////////////////////////////////////////////////
// matchbbsthreadget
// ネットワークより各種データを取得。
//
////////////////////////////////////////////////////////////////////////////////

int matchbbsthreadget(const char *host, const char *filename, BMallocIO &outbuf, http_info &info)
{
BMallocIO nbuf;
char exheader[256], requestheader[512];

	initexheader(exheader, info);

	sprintf(requestheader,
			"GET /%s/  HTTP/1.0\r\n"
			"Host: %s:80\r\n"
			"User-Agent: Monazilla/1.00 2ch+/" VERSION_2CHPLUS "\r\n"
			"Connection: close\r\n"
			"%s"
			"\r\n", filename,  host, exheader);
							
	PRINT(("requset =%s\n", requestheader));

	if (getData(requestheader, host, nbuf) != B_OK) return B_ERROR;

	size_t len = nbuf.BufferLength();
	const char *buf = static_cast<const char *>(nbuf.Buffer());
	
	checkResponseHeader(buf, info);
	
//	PRINT(("%s\n size %d sts:%d, gziped:%d, modified:%s\n", buf, info.header_length, info.status, info.gzipped, info.modified.String()));
	
	if (info.status != HTTP_OK ) {return info.status;}

	status_t sts = outbuf.Write(buf + info.header_length, len - info.header_length);	
	if (sts <= 0) return B_ERROR;

	html2dat dat(&outbuf, true);
	
	if(info.res_start > 1) return HTTP_PARTIAL_CONTENT;
	
	return HTTP_OK; 
}
